#include "ReadXml_15.1.hpp"

#include <tinyxml2.h>
#include <iostream>
#include <vector>
#include <memory>
#include <sstream>
#include <fstream>
/**
 * 注意！ 这个不是C标准库！ 这是GCC库！
 */
#include <cxxabi.h>

#include "WriteExcel_TypeDefine.hpp"

#include "main.hpp"

namespace
{
    /* xml的元素名 */
    std::string testsuite("testsuite");
    std::string testcase("testcase");
    std::string property("property");
    std::string properties("properties");

    [[deprecated]] [[maybe_unused]] typedef struct _GcovFunctionInfo
    {
        std::string className;
        std::string funcName;

        [[deprecated]] [[maybe_unused]] _GcovFunctionInfo(std::string allFuncName);
        [[deprecated]] [[maybe_unused]] std::string toString();

    } GcovFunctionInfo;

    void get_element(tinyxml2::XMLElement *element, gcov::XmlMap &xml_infoMap);
    void get_element_foreach(tinyxml2::XMLElement *element, std::shared_ptr<gcov::XmlInfo> xml_info);

} // namespace

std::shared_ptr<wxlsx::ExcelUtInfoList> gcov::Gcov_15_0::readXmlGcov(std::string xmlFilePath, std::string gcovFilePat)
{
    tinyxml2::XMLDocument xml_doc;

    tinyxml2::XMLError result = xml_doc.LoadFile(xmlFilePath.c_str());

    if (result != tinyxml2::XML_SUCCESS)
    {
        LOG("Error logding file: file name is %s, res is %d", xmlFilePath.c_str(), static_cast<int>(result));
        return std::make_shared<wxlsx::ExcelUtInfoList>();
    }

    tinyxml2::XMLElement *root = xml_doc.RootElement();
    if (!root)
    {
        LOG("No root element!");
        return std::make_shared<wxlsx::ExcelUtInfoList>();
    }

    XmlMap xml_infoMap;

    get_element(root->FirstChildElement(), xml_infoMap);

    this->m_xmlMap = std::make_shared<gcov::XmlMap>(xml_infoMap);

    LOG("debug log---------------------- output xmlMap ----------------------");
    m_xmlMap->foreach ([&](std::string keyName, std::shared_ptr<gcov::XmlInfo> data) -> void
                       { LOG("[%s] is {%s}", keyName.c_str(), data->toString().c_str()); });
    LOG("debug log---------------------- output xmlMap ----------------------");

    LOG("******************************************************************");
    std::shared_ptr<std::ifstream> gcovFile(new std::ifstream(gcovFilePat, std::ios::in), [](std::ifstream *data) -> void
                                            {
                                        if(data != nullptr)
                                        {
                                            data->close();
                                            delete data;
                                        } });
    if (!gcovFile->is_open())
    {
        LOG("Failed to open gcov file: %s", gcovFilePat.c_str());
        return std::make_shared<wxlsx::ExcelUtInfoList>();
    }

    std::string line;
    wxlsx::ExcelUtInfoList utInfoList;

    while (!gcovFile->eof())
    {
        std::getline(*gcovFile, line);
        /* make sure is a function */
        /* The function name may be '_ZN9ClassName8FuncName'. */
        if (line.find("Function \'_Z") != std::string::npos)
        {
            /* get func name */
            std::string allFuncName(strchr(line.c_str(), '\'') + 1, strrchr(line.c_str(), '\''));
            std::string translatedFuncName = demangle(allFuncName);
            this->m_xmlMap->foreach ([&](std::string key, std::shared_ptr<gcov::XmlInfo> xmlInfo) -> void
                                     {
                                        (void)key;
                                        
                                        std::shared_ptr<wxlsx::ExcelUtInfo> xlsxUtInfo;
                                        if (xmlInfo->className.empty()) // is a C function, or not class function.
                                        {
                                            if (translatedFuncName.find(xmlInfo->funcName) != std::string::npos)
                                            {
                                                xlsxUtInfo = std::make_shared<wxlsx::ExcelUtInfo>();
                                            }
                                        }
                                        else // C++ function, or is a class function.
                                        {
                                            if (translatedFuncName.find(xmlInfo->className + "::" + xmlInfo->funcName) != std::string::npos)
                                            {
                                                xlsxUtInfo = std::make_shared<wxlsx::ExcelUtInfo>();
                                            }
                                        }

                                        if (xlsxUtInfo != nullptr)
                                        {
                                            LOG("find func. funcname is %s", translatedFuncName.c_str());
                                            /* find this func */
                                            std::string _line = "";
                                            while (!gcovFile->eof() && std::getline(*gcovFile, _line) && _line.find("Function") == std::string::npos)
                                            {
                                                if (_line.find("Lines executed") != std::string::npos)
                                                {
                                                    xlsxUtInfo->linesExecuted = std::stod(_line.substr(_line.find(':') + 1, _line.find('%') - _line.find(':') - 1));
                                                    LOG("get linesExecuted: %lf", xlsxUtInfo->linesExecuted);
                                                }

                                                if (_line.find("Branches executed") != std::string::npos)
                                                {
                                                    xlsxUtInfo->branchesExecuted = std::stod(_line.substr(_line.find(':') + 1, _line.find('%') - _line.find(':') - 1));
                                                    LOG("get branchesExecuted: %lf", xlsxUtInfo->branchesExecuted);
                                                }
                                            }

                                            xlsxUtInfo->className = xmlInfo->className;
                                            xlsxUtInfo->methodName = xmlInfo->funcName;
                                            xlsxUtInfo->outputFileName = xmlInfo->fileName;
                                            xlsxUtInfo->caseNumber = xmlInfo->caseNumber;
                                            xlsxUtInfo->xmlCaseInfoList = xmlInfo->caseInfoList;
                                            xlsxUtInfo->funcOutCount = xmlInfo->funcOutCount;
                                            xlsxUtInfo->funcInCount = xmlInfo->funcInCount;

                                            if (xlsxUtInfo != nullptr)
                                            {
                                                utInfoList.push_back(xlsxUtInfo);
                                                LOG("Read xlsx info: %s", xlsxUtInfo->toString().c_str());
                                            }
                                        } });
        }
    }

    LOG("............................................................");
    utInfoList.foreach ([&](std::shared_ptr<wxlsx::ExcelUtInfo> info) -> void
                        { LOG("{%s}", info->toString().c_str()); });
    LOG("............................................................");

    return std::make_shared<wxlsx::ExcelUtInfoList>(utInfoList);
}

std::string gcov::Gcov_15_0::demangle(std::string &mangledName)
{
    int status;
    char *demangled = abi::__cxa_demangle(mangledName.c_str(), nullptr, nullptr, &status);

    if (status == 0)
    {
        std::string result(demangled);
        free(demangled);
        return result;
    }

    return mangledName;
}

namespace
{
    [[deprecated]] [[maybe_unused]]
    _GcovFunctionInfo::_GcovFunctionInfo(std::string allFuncName)
    {
        if (!allFuncName.empty())
        {
            char *_findLeftParen = strchr(allFuncName.c_str(), '(');
            if (_findLeftParen != nullptr)
                *_findLeftParen = '\0';
            else
                LOG("allFuncName is: %s", allFuncName.c_str());
            const char *_findFirstEmp = strchr(allFuncName.c_str(), ' ');
            if (_findFirstEmp != nullptr)
                _findFirstEmp++; // skip space
            else
                _findFirstEmp = allFuncName.c_str();

            if (_findLeftParen != nullptr)
            {
                const char *fcNameArray[allFuncName.size() / 2] = {};
                int fcNameLen = 0;
                fcNameArray[fcNameLen++] = _findFirstEmp;
                const char *tmp = strstr(_findFirstEmp, "::");
                for (; tmp != nullptr && fcNameLen < static_cast<int>(allFuncName.size()) / 2; tmp = strstr(fcNameArray[fcNameLen - 1], "::"))
                {
                    fcNameArray[fcNameLen++] = tmp + 2;
                }

                if (fcNameLen > 1) // C++ function name
                {
                    _findFirstEmp = fcNameArray[fcNameLen - 2];

                    const char *begin = _findFirstEmp;
                    std::string _className(begin, static_cast<const char *>(strstr(begin, "::")));
                    className = _className;

                    std::string _funcName(strstr(begin, "::") + 2, _findLeftParen);
                    funcName = _funcName;
                }
                else // C function name
                {
                    _findFirstEmp = fcNameArray[fcNameLen - 1];

                    const char *begin = _findFirstEmp;

                    std::string _funcName(begin, static_cast<const char *>(_findLeftParen));
                    funcName = _funcName;
                }
            }
        }
    }

    [[deprecated]] [[maybe_unused]]
    std::string _GcovFunctionInfo::toString()
    {
        std::stringstream ss;
        ss << className << "::" << funcName;
        return ss.str();
    }

    void get_element(tinyxml2::XMLElement *element, gcov::XmlMap &xml_infoMap)
    {
        if (element == nullptr)
            return;

        LOG("------------------------------------------------------------");

        const char *name = element->Name();
        if (name)
        {
            LOG("Element: %s", name);
        }

        std::shared_ptr<gcov::XmlInfo> xml_info = std::make_shared<gcov::XmlInfo>();

        if (strcmp(name, testsuite.c_str()) == 0)
        {
            std::string typeName = element->Attribute("name");
            xml_info->testsuiteName = typeName;
            xml_infoMap[typeName] = xml_info;
        }

        get_element_foreach(element->FirstChildElement(), xml_info);

        get_element(element->NextSiblingElement(), xml_infoMap);
    }

    void get_element_foreach(tinyxml2::XMLElement *element, std::shared_ptr<gcov::XmlInfo> xml_info)
    {
        if (element == nullptr)
            return;

        for (tinyxml2::XMLElement *element_tmp = element; element_tmp; element_tmp = element_tmp->NextSiblingElement())
        {
            int level = 2;
            const char *name = element_tmp->Name();
            if (name)
            {
                level += 2;
                LOG("%*sElement: %s", level, "", name);
            }

            if (strcmp(name, properties.c_str()) == 0)
            {
                get_element_foreach(element_tmp->FirstChildElement(), xml_info);
            }
            else if (strcmp(name, property.c_str()) == 0)
            {
                std::string proName = element_tmp->Attribute("name");
                if (proName.compare("input_para_function_name") == 0)
                {
                    xml_info->funcName = element_tmp->Attribute("value");
                }
                else if (proName.compare("input_para_class_name") == 0)
                {
                    xml_info->className = element_tmp->Attribute("value");
                }
                else if (proName.compare("input_para_pEvent_AppUseOnState") == 0)
                {
                    xml_info->caseInfoList.get(xml_info->caseNumber)->useOnStatus = element_tmp->Attribute("value");
                }
                else if (proName.compare("output_expected_result_auhRet") == 0)
                {
                    xml_info->caseInfoList.get(xml_info->caseNumber)->expected = element_tmp->Attribute("value");
                }
                else if (proName.compare("output_actual_result_auhRet") == 0)
                {
                    xml_info->caseInfoList.get(xml_info->caseNumber)->actual = element_tmp->Attribute("value");
                }
                else if (proName.compare("input_para_parameter_count") == 0)
                {
                    xml_info->funcInCount = element_tmp->Attribute("value");
                }
            }
            else if (strcmp(name, testcase.c_str()) == 0)
            {
                xml_info->caseNumber++;
                std::string _fileName = element_tmp->Attribute("file");
                xml_info->fileName = _fileName.substr(_fileName.find_last_of('\\') + 1, _fileName.find_last_of('.') - _fileName.find_last_of('\\') - 1);

                std::shared_ptr<gcov::XmlCaseInfo> caseInfo = std::make_shared<gcov::XmlCaseInfo>();
                caseInfo->caseID = xml_info->caseNumber;
                caseInfo->time = element_tmp->Attribute("time");
                caseInfo->name = element_tmp->Attribute("name");

                xml_info->caseInfoList.insert(caseInfo);
                get_element_foreach(element_tmp->FirstChildElement(), xml_info);
            }
        }
    }
} // namespace